﻿#region Using Directives

using System;
using System.Collections.Generic;
using System.IO;

#endregion Using Directives


namespace Kongo.Tags
{
    internal class IfTag : BaseTag
    {
        #region Fields

        private bool or;
        private Pair[] pairs;
        
        private bool captureIf;
        private bool captureElse;

        private List<Node> ifNodes;
        private List<Node> elseNodes;

        #endregion Fields


        #region Methods

        public override bool ChildNode(Node node)
        {
            if (captureIf)
            {
                BaseTag bt = node as BaseTag;
                if (bt != null && (bt.Args[0] == "end" || bt.Args[0] == "endif"))
                {
                    // End of 'if' block. There is no 'else' block.
                    captureIf = false;
                    ifNodes.TrimExcess();
                }
                else if (bt != null && bt.Args[0] == "else")
                {
                    // End of 'if' block. Start the 'else' block.
                    captureIf = false;
                    captureElse = true;
                    ifNodes.TrimExcess();
                }
                else
                {
                    // Add as a child of the 'if' block
                    ifNodes.Add(node);
                }

                return true;
            }

            if (captureElse)
            {
                BaseTag bt = node as BaseTag;
                if (bt != null && (bt.Args[0] == "end" || bt.Args[0] == "endif"))
                {
                    // End of 'else' block.
                    captureElse = false;
                    elseNodes.TrimExcess();
                }
                else
                {
                    // Add as a child of the 'else' block
                    elseNodes.Add(node);
                }

                return true;
            }

            return false;
        }


        public override void Render(Context context, TextWriter output)
        {
            List<Node> nodes = null;
            if (or)
            {
                // Pass if only one evaluates to 'true'
                bool pass = false;
                foreach (Pair pair in pairs)
                {
                    pass = (pair.Not ? !Helpers.IsTrue(context, pair.Variable) : Helpers.IsTrue(context, pair.Variable));
                    if (pass)
                    {
                        nodes = ifNodes;
                        break;
                    }
                }

                // Condition failed, use the else block (if there is one)
                if (!pass)
                    nodes = elseNodes;
            }
            else
            {
                // Fail if all don't evaluate to 'true'
                bool fail = false;
                foreach (Pair pair in pairs)
                {
                    fail = !(pair.Not ? !Helpers.IsTrue(context, pair.Variable) : Helpers.IsTrue(context, pair.Variable));
                    if (fail)
                    {
                        nodes = elseNodes;
                        break;
                    }
                }

                // Condition passed, use the if block
                if (!fail)
                    nodes = ifNodes;
            }

            // Render the block
            foreach (Node n in nodes)
                n.Render(context, output);
        }

        #endregion Methods


        #region Properties

        public override bool SupportsChildNodes
        {
            get { return true; }
        }

        #endregion Properties


        #region Constructors

        public IfTag(string[] args)  : base(args)
        {
            if (args.Length < 1)
                throw CreateSyntaxException(); // TODO Specific

            // Parse states
            const int START = 0;
            const int AND = 1;
            const int OR = 2;
            const int NOT = 3;
            const int VARIABLE = 4;
            
            // Parse
            bool or = false;
            bool and = false;
            int state = START;
            List<Pair> pairs = new List<Pair>();
            for(int i = 1; i < args.Length; i++)
            {
                string arg = args[i];
                switch (arg)
                {
                    case "and":
                        
                        // Previous state must be...
                        if (!(state == VARIABLE || state == NOT))
                            throw CreateSyntaxException(); // TODO Specific
                        
                        // Can't mix operators
                        if (or)
                            throw CreateSyntaxException(); // TODO Specific

                        and = true;
                        state = AND;
                        continue;

                    case "or":

                        // Previous state must be...
                        if (!(state == VARIABLE || state == NOT))
                            throw CreateSyntaxException(); // TODO Specific

                        // Can't mix operators
                        if (and)
                            throw CreateSyntaxException(); // TODO Specific

                        or = true;
                        state = OR;
                        continue;

                    case "not":

                        // Previous state must be...
                        if (!(state == START || state == AND || state == OR))
                            throw CreateSyntaxException(); // TODO Specific

                        state = NOT;
                        continue;

                    default:

                        // Assume this is a variable
                        Pair pair = new Pair();
                        pair.Not = state == NOT;
                        pair.Variable = new Variable(arg);
                        pairs.Add(pair);

                        state = VARIABLE;
                        continue;
                }
            }

            // Variable must be the last state
            if (state != VARIABLE)
                throw CreateSyntaxException(); // TODO Specific

            if (pairs.Count < 1)
                throw CreateSyntaxException(); // TODO Specific

            this.or = or;
            this.pairs = pairs.ToArray();
            this.ifNodes = new List<Node>();
            this.elseNodes = new List<Node>();
            this.captureIf = true;
        }

        #endregion Constructors


        #region Types

        public class IfTagCreator : ITagCreator
        {
            #region Methods

            public Tag Create(string[] args)
            {
                return new IfTag(args);
            }

            #endregion Methods
        }


        private struct Pair
        {
            public bool Not;
            public Variable Variable;
        }

        #endregion Types
    }
}
